package ponderation;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import model.Balise;
import model.Document;
import model.Mot;

/**
 * The Class TFIDFCalculator compute the TF and IDF
 * for a {@link Mot} in a {@link Balise}.
 */
public class TFIDFCalculator
{
  /** The documents. */
  private List<Document> documents;

  /**
   * A Map that links for each balise, the words of the balise and their
   * frequency.
   */
  private Map<String, Map<Mot, Double>> tfNumerator;

  /** A Map that links for each balise, the number of terms inside. */
  private Map<String, Double> tfDenominator;

  /** An integer that represent the total amount of balises. */
  private double idfNumerator;

  /** A Map that links, for each word, the number of balises it appears in. */
  private Map<Mot, List<Balise>> idfDenominator;

  /**
   * Instantiates a new tFIDF calculator.
   * 
   * @param documents
   *          the documents
   */
  public TFIDFCalculator(List<Document> documents)
  {
    this.documents = documents;
    preComputeTFIDF();
  }

  /**
   * Pre compute the term frequency and the inverted document frequency for the
   * given list of {@link Document}s.
   */
  private void preComputeTFIDF()
  {
    tfNumerator = new HashMap<>();
    tfDenominator = new HashMap<>();
    idfNumerator = 0;
    idfDenominator = new HashMap<>();

    for (Document document : documents)
    {
      for (Balise balise : document.getBalises())
      {
        Map<Mot, Double> frequencies = new HashMap<>();
        for (Mot terme : balise.getMots())
        {
          Double wordFrequency = frequencies.get(terme);
          frequencies.put(terme,
              wordFrequency == null ? Double.valueOf(1.0) : Double.valueOf(wordFrequency.intValue() + 1.0));

          if (idfDenominator.containsKey(terme))
          {
            List<Balise> listOfBalises = idfDenominator.get(terme);
            if (!listOfBalises.contains(balise))
            {
              listOfBalises.add(balise);
              idfDenominator.put(terme, listOfBalises);
            }
          }
          else
          {
            idfDenominator.put(terme, new ArrayList<>(Arrays.asList(balise)));
          }
        }

        tfDenominator.put(balise.getUniqueID(), Double.valueOf(balise.getMots().size()));
        tfNumerator.put(balise.getUniqueID(), frequencies);
      }
      idfNumerator += document.getBalises().size();
    }
  }

  /**
   * Gets the terms for balise.
   * 
   * @param baliseUniqueId
   *          the balise unique id
   * @return the terms for balise
   */
  public Set<Mot> getTermsForBalise(String baliseUniqueId)
  {
    return tfNumerator.get(baliseUniqueId).keySet();
  }

  /**
   * Gets the Term Frequency.<br>
   * <br>
   * The TF is computed with the formula : <br>
   * <em>TF = Occurence of a {@link Mot} in the {@link Balise} 
   * <b>/</b> Total number of {@link Mot} in a {@link Balise}
   * 
   * @param mot the {@link Mot} to get the TF
   * @param baliseUniqueId the unique ID of the {@link Balise}
   * @return the tf
   */
  public double getTF(Mot mot, String baliseUniqueId)
  {
    double tfNum = tfNumerator.get(baliseUniqueId).get(mot).doubleValue();
    double tfDenom = tfDenominator.get(baliseUniqueId).doubleValue();
    return tfNum / tfDenom;
  }

  /**
   * Gets the Inverse Document Frequency.<br>
   * <br>
   * The IDF is computed with the formula : <br>
   * <em>IDF = {@link Math#log(double)}(Total number of {@link Balise}s from ALL the 
   * {@link Document}s <b>/</b> Total number of {@link Balise}s that contains the {@link Mot} 
   * in ALL the {@link Document}s )</em>
   * 
   * @param mot the word to get the IDF
   * @return the idf
   */
  public double getIDF(Mot mot)
  {
    return Math.log10(idfNumerator / idfDenominator.get(mot).size());
  }
}
